package common

import (
	"commerce/cache"
	"commerce/model"
	"context"
	"encoding/json"
	"fmt"
	jwt "github.com/dgrijalva/jwt-go"
	"io/ioutil"
	"net/http"
	"path/filepath"
	"time"
)

const (
	privateKeyPath = "keys/app.rsa"
	publicKeyPath  = "keys/app.rsa.pub"
	JWT            = "jwtUser" // 后台管理系统用户
	APP            = "appUser" // applet 会员
)

var (
	verifyKey, signKey []byte
)

// 后台管理系统用户

type JwtUser struct {
	Uid   int
	Uname string
}

func initKeys() {

	var err error

	signKey, err = ioutil.ReadFile(filepath.Join(CurDir, privateKeyPath))
	if err != nil {
		Warn.Fatalf("[initKey]:%s\n", err)
	}
	verifyKey, err = ioutil.ReadFile(filepath.Join(CurDir, publicKeyPath))
	if err != nil {
		Warn.Fatalf("[initKey]:%s\n", err)
		panic(err)
	}
}

func GetVK() []byte {
	return verifyKey
}

func GenerateJWT(user model.User) (string, error) {

	// t := jwt.New(jwt.GetSigningMethod("RS256"))
	t := jwt.New(jwt.SigningMethodHS256)

	t.Claims = jwt.MapClaims{
		// issuer
		"iss":      "ss",
		"userInfo": JwtUser{user.Id, user.Username},
		"exp":      time.Now().Add(COOKIE_MAX_AGE * time.Second).Unix(),
	}
	tokenstring, err := t.SignedString(signKey)
	if err != nil {
		return "", err
	}
	return tokenstring, nil
}

func ParseJwtUser(r *http.Request) (*JwtUser, error) {

	cookie, err := r.Cookie(COOKIE_NAME)
	if err != nil {
		return nil, err
	}
	return ParseJwtByToken(cookie.Value)
}

func ParseJwtByToken(tok string) (*JwtUser, error) {

	token, err := jwt.Parse(tok, func(token *jwt.Token) (interface{}, error) {
		return signKey, nil
	})
	if err != nil {
		return nil, err
	}
	if !token.Valid {
		return nil, fmt.Errorf("invalid token")
	}
	switch token.Claims.(type) {
	case jwt.MapClaims:
		ui := token.Claims.(jwt.MapClaims)["userInfo"]
		bytes, _ := json.Marshal(ui)
		var jwtUser JwtUser
		err := json.Unmarshal(bytes, &jwtUser)
		if err != nil {
			return nil, err
		}
		cacheToken, err := cache.Get(fmt.Sprintf("%s:%d", "USER", jwtUser.Uid))
		if err != nil {
			return nil, err
		}
		if cacheToken != token.Raw {
			return nil, fmt.Errorf("invalid token input")
		}
		return &jwtUser, nil
	default:
		return nil, fmt.Errorf("invalid claim")
	}
}

func Authorize(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

	cookie, err := r.Cookie(COOKIE_NAME)
	if err != nil {
		//Templates["login"].Execute(w, "无效token,请重新登录")
		http.Redirect(w, r, "/login.html", http.StatusFound)
		return
	}
	token, err := jwt.Parse(cookie.Value, func(token *jwt.Token) (interface{}, error) {
		// return verifyKey, nil
		// 因为使用的是Mac，不是rsa, 所以都是signkey
		return signKey, nil
	})
	if err != nil {
		switch err.(type) {

		case *jwt.ValidationError:
			vErr := err.(*jwt.ValidationError)
			switch vErr.Errors {
			case jwt.ValidationErrorExpired:
				//DisplayAppErr(w, err, "Access token 过期了，重新获取一个！", http.StatusUnauthorized)
				http.Redirect(w, r, "/login.html", http.StatusFound)
				return
			default:
				//DisplayAppErr(w, err, "parse token 出错)_(", http.StatusInternalServerError)
				http.Redirect(w, r, "/login.html", http.StatusFound)
				return
			}
		default:
			//DisplayAppErr(w, err, "parse token 出错)_(:", http.StatusInternalServerError)
			http.Redirect(w, r, "/login.html", http.StatusFound)
			return
		}
	}

	if token.Valid {
		// 解析token, 提取用户信息, 查询redis缓存，验证redis中的token是否和当前token一致
		// 如果 ok, 更新token的有效期，如果ttl < 5分钟，自动续期，19分钟
		mapClaims, ok := token.Claims.(jwt.MapClaims)
		if !ok || mapClaims == nil {
			http.Redirect(w, r, "/login.html", http.StatusFound)
			return
		}
		jwtUserMap, ok := mapClaims["userInfo"].(map[string]interface{})
		if !ok {
			http.Redirect(w, r, "/login.html", http.StatusFound)
			return
		}
		uid := int(jwtUserMap["Uid"].(float64))
		key := fmt.Sprintf("%s:%d", "USER", uid)
		cacheToken, err := cache.Get(key)
		if err != nil || cacheToken != token.Raw {
			//Templates["login"].Execute(w, "无效token,请重新登录")
			http.Redirect(w, r, "/login.html", http.StatusFound)
			return
		}
		// 把当前合法登录用户 保存在上下文中
		r = r.WithContext(context.WithValue(context.Background(),
			JWT, &JwtUser{uid, jwtUserMap["Uname"].(string)}))
		// 续组
		//ttl, err := cache.TTL(key)
		//if err == nil && ttl < 300 {
		//	// 重新生成token
		//	newJWT, err := GenerateJWT(model.User{Id: uid, Username: jwtUserMap["Uname"].(string)})
		//	if err == nil {
		//		cache.SetTTL(key, COOKIE_MAX_AGE)
		//		http.SetCookie(w, &http.Cookie{Name: COOKIE_NAME, Value: newJWT, MaxAge: COOKIE_MAX_AGE, Domain: AppConfig.CookieDomain})
		//	}
		//}
		next(w, r)
	} else {
		//Templates["login"].Execute(w, "无效token,请重新登录")
		http.Redirect(w, r, "/login.html", http.StatusFound)
	}
}
